﻿package com.duartepeixinho.pTween 
{
	import flash.display.Shape;
	import flash.events.Event;
	import flash.utils.Dictionary;
	import flash.utils.getTimer;
	import flash.utils.getDefinitionByName;
	
	import com.duartepeixinho.pTween.core.tweenEquations;
	import com.duartepeixinho.pTween.core.tweenObj;
	
	/**
	 * pTweenEngine 1.1
	 * @author Duarte Peixinho
	 */
	public class pTweenEngine
	{		
		
		private static var _engineInitiated:Boolean = false;
		
		// ticker
		private static var _ticker:Shape;
		// tweens list
		private static var _listTweens:Dictionary = new Dictionary(true);
		// current target
		private var _currentTarget:Object;	
		
		// pause
		private static var _pauseInit:Number = 0;
		private static var _pauseEnd:Number = 0;
		private static var _pause:Boolean = false;
		
		// reverse
		private static var _reverse:Boolean = false;
		
		// plugins
		private static var _plugins:Dictionary;
		private static var _pluginsLoaded:Boolean = false;		
		private static var _specialProperties:Dictionary;
		
		// animateByFrame
		private static var _frame:uint = 0;
		
		/**
		 * Constructor
		 * @param	o
		 */
		public function pTweenEngine(o:Object) {
			
			// engine trace
			if (!_engineInitiated) {
				trace("pTween v1.1, a tween engine by Duarte Peixinho");
				_engineInitiated = true;
			}
			
			if (!_ticker) createTicker();
			// creates tween object			
			if (isTweening(o)) kill(o);
			_listTweens[o] = new tweenObj(o);
			// sets initial time
			_listTweens[o].initTime = getTime();
			// sets current target
			_currentTarget = o;			
		}
		
		// -------------------------------------------------------------------------------- //
		//
		//	Tween Properties
		//
		//--------------------------------------------------------------------------------- //		
		
		/**
		 * Sets "to" params
		 * @param	duration
		 * @param	...args
		 */
		public function to(duration:Number, params:Object):pTweenEngine {
			_listTweens[_currentTarget].duration = duration;
			_listTweens[_currentTarget].endTime = duration + getTime();			
			_listTweens[_currentTarget].endParams = params;			
		
			for (var s:String in params) 
				if (!_listTweens[_currentTarget].initParams[s])
					_listTweens[_currentTarget].initParams[s] = _currentTarget[s];			
			
			// sets default easing equation
			_listTweens[_currentTarget].easing = tweenEquations.linear;
			
			return this;
		}
		
		/**
		 * Sets initial params
		 * @param	params
		 * @return	pTweenEngine
		 */
		public function from(params:Object):pTweenEngine {
			_listTweens[_currentTarget].initParams = params;
			
			// set initial values on target
			for (var s:String in params) {
				_currentTarget[s] = params[s];
			}
			
			return this;
		}
		
		/**
		 * Sets autoAlpha
		 * @param	alpha
		 * @return
		 */
		public function autoAlpha(alpha:Number = 0):pTweenEngine {
			if (!_listTweens[_currentTarget].endParams) _listTweens[_currentTarget].endParams = new Object();
			_listTweens[_currentTarget].endParams["alpha"] = alpha;
			_listTweens[_currentTarget].initParams["alpha"] = _currentTarget.alpha;
			_listTweens[_currentTarget].autoAlpha = true;
			
			return this;
		}
		
		/**
		 * Sets easing
		 * @param	easing
		 */
		public function easing(easing:String):pTweenEngine {
			_listTweens[_currentTarget].easing = easing;
			
			return this;
		}
		
		/**
		 * Sets delay
		 * @param	delay		 
		 */
		public function delay(delay:Number):pTweenEngine {
			_listTweens[_currentTarget].delay = delay;
			_listTweens[_currentTarget].initTime += delay;
			_listTweens[_currentTarget].endTime += delay;
			
			return this;
		}
		
		/**
		 * Sets onComplete Event
		 * @param	onComplete
		 * @param	params
		 */
		public function onComplete(onComplete:Function,...params):pTweenEngine {
			_listTweens[_currentTarget].onComplete = onComplete;			
			if (params) _listTweens[_currentTarget].onCompleteParams = params;
			
			return this;
		}
		
		/**
		 * Sets onEnterFrame Event
		 * @param	onEnterFrame
		 * @param	params		 
		 */
		public function onEnterFrame(onEnterFrame:Function, ...params):pTweenEngine {
			_listTweens[_currentTarget].onEnterFrame = onEnterFrame;
			if (params) _listTweens[_currentTarget].onEnterFrameParams = params;
			
			return this;
		}
		
		/**
		 * Sets onStart Event
		 * @param	onStart
		 * @param	params		 
		 */
		public function onStart(onStart:Function, ...params):pTweenEngine {
			_listTweens[_currentTarget].onStart = onStart;
			_listTweens[_currentTarget].onStartDone = false;
			if (params) _listTweens[_currentTarget].onStartParams = params;
			
			return this;
		}
		
		/**
		 * Tween By Frames
		 */
		public function tweenByFrames(byFrames:Boolean = true):pTweenEngine {
			_listTweens[_currentTarget].tweenByFrames = byFrames;
			
			return this;
		}
		
		public function yoyo():pTweenEngine {
			_listTweens[_currentTarget].yoyo = true;
			
			return this;
		}
		
		/**
		 * use plugins
		 * @param	params
		 */
		public function plugin(params:Object):pTweenEngine {	
			// sets special properties on
			_listTweens[_currentTarget].specialPropertiesOn = true;				
			for (var s:String in params) {
				if (_specialProperties[s])
					// adds property to target
					_listTweens[_currentTarget].addSpecialProperty(s, params[s]);
				else throw "Error: Necessary Plugin not Loaded for " + s;
			}
				
			return this;
		}
		
		// -------------------------------------------------------------------------------- //
		//
		//	Static Methods
		//
		//--------------------------------------------------------------------------------- //
		
		/**
		 * kills all tweens
		 */
		public static function KillAllTweens():void {
			for each (var t:tweenObj in _listTweens) {
				delete _listTweens[t.target];
			}
		}
		
		/**
		 * Delete Tween
		 * @param	o
		 */
		public static function kill(o:Object):void {
			if (pTweenEngine.isTweening(o))
				delete _listTweens[o];
		}
		
		/**
		 * Locates Tween
		 * @param	o
		 * @return	boolean
		 */
		public static function isTweening(o:Object):Boolean {
			if (_listTweens[o]) return true;
			
			return false;
		}
		
		/**
		 * Pauses all Tweens
		 */
		public static function pauseAllTweens():void {
			_ticker.removeEventListener(Event.ENTER_FRAME, tick);
			_pause = true;
			_pauseInit = getTime();
		}
		
		/**
		 * Resumes all Tweens
		 */
		public static function resumeAllTweens():void {			
			_pause = false;
			_pauseEnd = getTime();
			for each (var t:Object in _listTweens) {
				_listTweens[t.target].pauseInit = _pauseInit;
				_listTweens[t.target].pauseEnd = _pauseEnd;
			}
			_ticker.addEventListener(Event.ENTER_FRAME, tick);
		}
		
		/**
		 * Returns tweens/tween (if specified) pause state
		 * @param	o	Object
		 * @return
		 */
		public static function isPaused(o:Object = null):Boolean {
			if (!o) return _pause;
			else if (isTweening(o)) return _listTweens[o].pause;
			else return false;
		}
		
		/**
		 * Pauses a specific tween object
		 * @param	o	Object
		 */
		public static function pauseTween(o:Object):void {
			if (isTweening(o)) {
				_listTweens[o].pauseInit = getTime();				
			}
		}
		
		/**
		 * Resumes a specific tween object
		 * @param	o	Object
		 */
		public static function resumeTween(o:Object):void {
			if (isTweening(o)) {
				_listTweens[o].pauseEnd = getTime();	
			}			
		}		
		
		/**
		 * Reverse Alt Tweens
		 */
		public static function reverseAllTweens():void {
			for each (var t:tweenObj in _listTweens) reverseTween(t.target);
		}
		
		/**
		 * Reverse a specific tween object
		 * @param	o	Object
		 */
		public static function reverseTween(o:Object):void {
			if (isTweening(o)) {
				if (!_listTweens[o].reverse) {
					if (_listTweens[o].tweenByFrames) _listTweens[o].setReverse(true, getTime(true));
					else _listTweens[o].setReverse(true, getTime());
				}
				else {
					if (_listTweens[o].tweenByFrames) _listTweens[o].setReverse(false, getTime(true));
					else _listTweens[o].setReverse(false, getTime());
				}
			}
		}		
		
		/**
		 * Resets All Tweens
		 */
		public static function resetAllTweens():void {
			for each (var t:Object in _listTweens) resetTween(t.target);
		}
		
		/**
		 * Returns if all/specific tween is reversed
		 * @param	o	Object
		 * @return		Boolean
		 */
		public static function isReversed(o:Object = null):Boolean {
			if (!o) return _reverse;
			else if (isTweening(o)) return _listTweens[o].reverse;
			else return false;
		}
		
		/**
		 * Resets a specific tween
		 * @param	o	Object
		 */
		public static function resetTween(o:Object):void {
			if (isTweening(o)) {				
				if (!_listTweens[o].tweenByFrames) {
					_listTweens[o].initTime = getTime();
					_listTweens[o].endTime = getTime() + _listTweens[o].duration;
				}
				else {
					_listTweens[o].initTime = getTime(true);
					_listTweens[o].endTime = getTime(true) + _listTweens[o].duration;
				}
			}
		}
		
		// -------------------------------------------------------------------------------- //
		//
		//	Private Methods
		//
		// -------------------------------------------------------------------------------- //		
		
		/**
		 * Updates all tweens
		 * @param	time
		 */
		private static function updateTweens(time:Number):void {	
			
			for each (var t:tweenObj in _listTweens) {
				if (!t.pause) {
					
					// tween by frames change the time
					if (t.tweenByFrames) time = getTime(true);
					
					if (time >= t.initTime)	{				
						// on Start
							if (!t.onStartDone) {
								if (t.onStartParams) {							
								t["onStart"].apply(null, t["onStartParams"]);
							}
							else t["onStart"]();	
							t.onStartDone = true;
						}
						
						var s:String;
						var plugin:String;
						
						// tween
						if (t.reverse) {								
							var timeAux:Number = (t.reverseTime - (time - t.reverseTime));							
							for (s in t.endParams) {
								t.target[s] = tweenEquations.calculateEquation(t.easing, timeAux - t.initTime, t.initParams[s], t.endParams[s] - t.initParams[s], t.duration);								
							}
							// special plugins
							if (_pluginsLoaded && t.specialPropertiesOn) {
								for each (plugin in _plugins) {											
									getDefinitionByName("com.duartepeixinho.pTween.plugins." + plugin).update(t, timeAux-t.initTime);
								}
							}
						}
						else {						
							for (s in t.endParams) {		
								t.target[s] = tweenEquations.calculateEquation(t.easing, time-t.initTime, t.initParams[s], t.endParams[s] - t.initParams[s], t.duration);
							}
							// special plugins
							if (_pluginsLoaded && t.specialPropertiesOn) {
								for each (plugin in _plugins) {									
									getDefinitionByName("com.duartepeixinho.pTween.plugins." + plugin).update(t, time-t.initTime);
								}
							}
						}
						
					}
					// on enterFrame
					if (t.onEnterFrame is Function) {
						if (t.onEnterFrameParams) {							
							t["onEnterFrame"].apply(null, t["onEnterFrameParams"]);
						}
						else t["onEnterFrame"]();
					}
					
					// check if tween ended					
					if ((time >= t.endTime && !t.reverseTime) || (timeAux <= t.initTime)) {						
						if (t.autoAlpha && t.target.alpha == 0 && !t.yoyo) {
							t.target.visible = false;
						}
						else if (t.yoyo) {
							reverseTween(t.target);
							t.yoyo = false;							
						}
						// on Complete
						else if (t.onComplete is Function) {
							// kill tween
							var c:Function = t.onComplete;
							var cp:Array = t.onCompleteParams;
							kill(t.target);
							// on Complete
							if (cp) {							
								c.apply(null, cp);
							}
							else c();
						}
						else {
							kill(t.target);
						}
					}			
				}	
			}
		}
		
		/**
		 * Creates Ticker
		 */
		private static function createTicker():void {
			_ticker = new Shape();
			_ticker.addEventListener(Event.ENTER_FRAME, tick);
		}
		
		/**
		 * Ticks		 
		 */
		private static function tick(e:Event):void {
			_frame++;
			updateTweens(getTime());
		}
		
		/**
		 * Timer
		 * @return	Number	Time
		 */
		private static function getTime(frames:Boolean=false):Number {	
			if (frames)
				return _frame;
				
			else return getTimer() / 1000;
		}
		
		// -------------------------------------------------------------------------------- //
		//
		//	Special Features
		//
		// -------------------------------------------------------------------------------- //
		
		/**
		 * Registers a Plugin
		 * @param	plugin
		 */
		public static function registerPlugin(plugin:String):void {
			if (!_pluginsLoaded) {				
				_plugins = new Dictionary();
				_specialProperties = new Dictionary();
				_pluginsLoaded = true;
			}
			if (!_plugins[plugin]) {				
				_plugins[plugin] = plugin;
				trace("Plugin Loaded: " + plugin);
			}
		}
		
		/**
		 * Registers a special property
		 * @param	property
		 */
		public static function registerProperty(property:String):void {
			_specialProperties[property] = property;
		}
	}
}